package il.ac.bgu.soa2009.hw6.impl;

import il.ac.bgu.soa2009.hw5.ClientPortType;
import il.ac.bgu.soa2009.hw5.RestaurantServiceServiceLocator;
import il.ac.bgu.soa2009.hw5.schema.BooleanResponse;
import il.ac.bgu.soa2009.hw5.schema.RestaurantDetails;
import il.ac.bgu.soa2009.hw5.schema.Time;
import il.ac.bgu.soa2009.hw5.schema.ReservationName;
import il.ac.bgu.soa2009.hw6.CinemaServiceServiceLocator;
import il.ac.bgu.soa2009.hw6.NightOutDetails;
import il.ac.bgu.soa2009.hw6.Ticket;
import il.ac.bgu.soa2009.hw6.TicketIdentifier;
import il.ac.bgu.soa2009.hw6.cinema.Movie;
import org.apache.axis.AxisFault;
import org.apache.log4j.Logger;

import javax.xml.rpc.ServiceException;
import java.rmi.RemoteException;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;


public class GoOutServiceImpl {
    private  static int REST_COUNT = 2;
    private  static int CINE_COUNT = 2;
    private static Logger logger = Logger.getLogger(GoOutServiceImpl.class);
    private final Map<String, List<Date>> activeReservations = new HashMap<String, List<Date>>();
    private final List<ReservationRequest> makeAplanToGoOutRequests = Collections.synchronizedList(new LinkedList<ReservationRequest>());
    private final ExecutorService executorService = Executors.newFixedThreadPool(2);
    private RestaurantServiceServiceLocator[] restaurantLocators;
    private CinemaServiceServiceLocator[] cinemaLocators;

    public GoOutServiceImpl() {
        String[] restaurantServers = ServerURIReader.getServers("restaurant.txt");
        int restaurantLength = restaurantServers.length;
        restaurantLocators = new RestaurantServiceServiceLocator[restaurantLength];
        for (int i=0;i<restaurantLength;i++) {
            restaurantLocators[i] = new RestaurantServiceServiceLocator();
            restaurantLocators[i].setClientRestaurantServiceEndpointAddress(restaurantServers[i]);
        }

        String[] cinemaServers = ServerURIReader.getServers("cinema.txt");
        int cinemaLength = cinemaServers.length;
        cinemaLocators = new CinemaServiceServiceLocator[cinemaLength];
        for (int i=0;i<cinemaLength;i++) {
            cinemaLocators[i] = new CinemaServiceServiceLocator();
            cinemaLocators[i].setCinemaClientServiceEndpointAddress(cinemaServers[i]);
        }
    }

    public boolean allreadyResgistered(java.lang.String reservationName, java.util.Date date) {
        List<Date> list = activeReservations.get(reservationName);
        if (list != null) {
            for (Date dateElement : list) {
                if (equals(date, dateElement)) {
                    return true;
                }
            }
        }//else
        return false;
    }

    @SuppressWarnings({"deprecation"})
    private boolean equals(java.util.Date date, Date dateElement) {
        return (date.getYear() == dateElement.getYear()) & (date.getMonth() == dateElement.getMonth()) & (date.getDay() == dateElement.getDay());
    }

    public java.lang.String[] getRestaurantTypes() {
        Set<String> resSet = new LinkedHashSet<String>();
        for (int i = 0; i < REST_COUNT; i++) {
            try {
                ClientPortType clientRestaurantService = restaurantLocators[i].getClientRestaurantService();
                RestaurantDetails restaurantDetails = clientRestaurantService.getRestaurantDetails();
                String type = restaurantDetails.getType().toString();
                if (!"NotSet".equalsIgnoreCase(type)) {
                    resSet.add(type);
                }
            } catch (ServiceException e) {
                logger.error("Cannot acsess restaurant: " + i, e);
            } catch (RemoteException e) {
                logger.error("Error ocured while acssesing restaurant " + i + " getMovies()", e);
            }
        }
        String[] res = new String[resSet.size()];
        resSet.toArray(res);
        return res;
    }

    public java.lang.String[] getMovies() {
        Set<String> resSet = new LinkedHashSet<String>();
        for (int i = 0; i < CINE_COUNT; i++) {
            try {
                il.ac.bgu.soa2009.hw6.ClientPortType cinemaClientService = cinemaLocators[i].getCinemaClientService();
                Movie[] movies = cinemaClientService.getMovies();
                for (Movie movie : movies) {
                    resSet.add(movie.getMovieName());
                }
            } catch (ServiceException e) {
                logger.error("Cannot acsess cinema: " + i, e);
            } catch (RemoteException e) {
                logger.error("Error ocured while acssesing cinema " + i + " getMovies()", e);
            }
        }
        String[] res = new String[resSet.size()];
        resSet.toArray(res);
        return res;
    }

    public void makeAplanToGoOut(java.lang.String reservationName, java.lang.String movieName, int ticketCount, il.ac.bgu.soa2009.hw5.schema.RestaurantType restaurantType, il.ac.bgu.soa2009.hw5.schema.Time time, java.util.Date date) {
        String errMsg = "reservation for " + reservationName + " at " + date + ", is not processed yet";
        il.ac.bgu.soa2009.hw6.Ticket ticket = new il.ac.bgu.soa2009.hw6.Ticket(new TicketIdentifier(-1, -1), new int[0]);
        NightOutDetails nightOutDetails = new NightOutDetails(false, errMsg, reservationName, date, time, ticket);
        ReservationRequest reservationRequest = new ReservationRequest(nightOutDetails, reservationName, date);
        makeAplanToGoOutRequests.add(0, reservationRequest);
        RestaurntReservationTask reserveTableTask = new RestaurntReservationTask(reservationName, restaurantType, time, date, restaurantLocators);
        MovieReservationTask movieReservationTask = new MovieReservationTask(movieName, ticketCount, date, cinemaLocators);
        Future<BooleanResponse> tableReservationResultFuture = executorService.submit(reserveTableTask);
        Future<il.ac.bgu.soa2009.hw6.cinema.Ticket> ticketReservationResultFuture = executorService.submit(movieReservationTask);
        try {//Restaurant reservation success
            BooleanResponse response = tableReservationResultFuture.get();
            //nightOutDetails.setErrorDescription(response.getShortDescription());
            errMsg = response.getShortDescription();
        } catch (ExecutionException e) {//Restaurant reservation fail
            Throwable cause = e.getCause();
            if (cause instanceof AxisFault) {
                AxisFault axisFault = (AxisFault) cause;
                nightOutDetails.setErrorDescription("Fail getting a table : " + axisFault.getFaultString());
            } else {
                nightOutDetails.setErrorDescription("Fail getting a table : " + cause);
            }
            return;
        } catch (InterruptedException e) {
            nightOutDetails.setErrorDescription("Interapted while trying to get a table");
            return;
        }
        try {//every thing is good
            il.ac.bgu.soa2009.hw6.cinema.Ticket cinemaTicket = ticketReservationResultFuture.get();
            ticket = cinemaTicketToTicket(cinemaTicket);
            nightOutDetails.setTicket(ticket);
            nightOutDetails.setIsSuccessful(true);
            errMsg = errMsg + "\n" + ticketCount + " ticket(s) were reserved for you for the movie: " + movieName;
            nightOutDetails.setErrorDescription(errMsg);
            updateActiveReservations(activeReservations, reservationName, date);
        } catch (ExecutionException e) {//ticket reservation failed : explain why and cancel restaurant reservation
            Throwable cause = e.getCause();
            if (cause instanceof AxisFault) {
                AxisFault axisFault = (AxisFault) cause;
                nightOutDetails.setErrorDescription(axisFault.getFaultString());
            } else {
                if (cause instanceof ServiceException) {
                    nightOutDetails.setErrorDescription("unable to access cinema service ");
                } else {
                    if (cause instanceof NoSuchMovieException) {
                        nightOutDetails.setErrorDescription("No such movie: " + movieName);
                    } else {
                        nightOutDetails.setErrorDescription("Error : " + e);
                    }
                }
            }
            cancelRestaurantReservation(reservationName, reserveTableTask);
        } catch (InterruptedException e) {
            nightOutDetails.setErrorDescription("Interapted while trying to get a ticket");
            cancelRestaurantReservation(reservationName, reserveTableTask);
        }
    }

    private void cancelRestaurantReservation(String reservationName, RestaurntReservationTask reserveTableTask) {
        try {
            ClientPortType clientRestaurantService = reserveTableTask.getUsedLocator().getClientRestaurantService();
            clientRestaurantService.cancelReservation(new ReservationName(reservationName));
        } catch (ServiceException e1) {
            logger.error("Could not reach the restaurant service to cancel reservation", e1);
        } catch (RemoteException e1) {
            logger.error("Error while trying to cancel reservation", e1);
        }
    }

    private void removeFromActiveReservations(String reservationName, Date date) {
        List<Date> list = activeReservations.get(reservationName);
        if (list != null) {
            for (Iterator<Date> iterator = list.iterator(); iterator.hasNext();) {
                Date dateElement = iterator.next();
                if (equals(date, dateElement)) {
                    iterator.remove();
                }
            }
        }

    }

    private Ticket cinemaTicketToTicket(il.ac.bgu.soa2009.hw6.cinema.Ticket ticket) {
        return new Ticket(new TicketIdentifier(ticket.getId().getReservationId(), ticket.getId().getHallNumber()), ticket.getSeat());
    }

    private void updateActiveReservations(Map<String, List<Date>> activeReservations, String reservationName, Date date) {
        List<Date> list = activeReservations.get(reservationName);
        if (list == null) {
            list = new LinkedList<Date>();
            activeReservations.put(reservationName, list);
        }
        list.add(date);
    }

    public NightOutDetails getTheResults(String reservationName, java.util.Date date) {
        ReservationRequest reservationRequest = getReservationRequest(makeAplanToGoOutRequests, reservationName, date);
        if (reservationRequest == null) {
            return new NightOutDetails(false, "reservation for " + reservationName + " at " + date + ", does not exsist", reservationName, date, Time.value1, new Ticket(new TicketIdentifier(-1, -1), new int[0]));
        } else {
            return reservationRequest.nightOutDetails;
        }
    }

    private ReservationRequest getReservationRequest(List<ReservationRequest> makeAplanToGoOutRequests, String reservationName, Date date) {
        for (ReservationRequest makeAplanToGoOutRequest : makeAplanToGoOutRequests) {
            if (makeAplanToGoOutRequest.reservationName.equals(reservationName) && equals(date, makeAplanToGoOutRequest.date)) {
                return makeAplanToGoOutRequest;
            }
        }
        return null;
    }

    public static int getRestaurantCount() {
        return REST_COUNT;
    }

    public static int getCinemaCount() {
        return CINE_COUNT;
    }

    private class ReservationRequest {
        private NightOutDetails nightOutDetails;
        private String reservationName;
        private Date date;

        public ReservationRequest(NightOutDetails nightOutDetails, String reservationName, Date date) {
            this.nightOutDetails = nightOutDetails;
            this.reservationName = reservationName;
            this.date = date;
        }
    }
}
